﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using SbsSW.SwiPlCs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Adventure.Model
{
    internal class Universe
    {
        #region Constructors

        internal Universe(Rectangle bounds)
        {
            _bounds = bounds;
        }

        #endregion

        #region Private Fields

        private Rectangle _bounds;
        private List<Scenario> _scenarios;
        private Scenario _currentScenario;
        private List<Item> _items;
        private Dragon _yorgle;
        private Dragon _grundle;
        private Character _character;
        private int _time = 0;
        private int _lastUpdateTime = 0;
        private Scenario _lastCurrentScenario;

        #endregion

        #region Attributes and Properties

        private bool _gameEnded = false;
        public bool GameEnded
        {
            get { return _gameEnded; }
        }

        #endregion

        #region Internal Methods

        internal void Initialize()
        {
            this.CreateScenarios();
            this.CreateCharacter();
            this.CreateItems();
            this.CreateYorgle();
            this.CreateGrundle();

            foreach (var scenario in _scenarios)
                scenario.Initialize();

            _character.Initialize();

            foreach (var item in _items)
                item.Initialize();

            _yorgle.Initialize();
            _grundle.Initialize();
        }

        internal void Update(GameTime gameTime)
        {
            //PlQuery.PlCall("open('result.pl', write, S), set_output(S), listing, close(S)");

            using (var currentScenarioQuery = new PlQuery(string.Format("cenarioCorrente(C, {0})", _time)))
            {
                var currentScenarioData = currentScenarioQuery.SolutionVariables.First();
                var currentScenarioName = currentScenarioData["C"].ToString();
                _currentScenario = _scenarios.First(s => s.Name == currentScenarioName);
            }

            using (var endGameQuery = new PlQuery(string.Format("fimJogo({0}, S)", _time)))
            {
                var currentScenarioData = endGameQuery.SolutionVariables.First();
                _gameEnded = currentScenarioData["S"].ToString() == "1" ? true : false;
            }

            _character.Update(gameTime, _currentScenario, _time);

            foreach (var item in _items)
                item.Update(gameTime, _currentScenario, _time);

            _yorgle.Update(gameTime, _currentScenario, _time);
            _grundle.Update(gameTime, _currentScenario, _time);

            foreach (var scenario in _scenarios)
                scenario.Update(gameTime, _time);

            this.UpdateData();

            _time++;
        }

        internal void Draw(GameTime gameTime)
        {
            _currentScenario.Draw(gameTime);

            foreach (var item in _items)
                item.Draw(gameTime);

            _character.Draw(gameTime);
            _yorgle.Draw(gameTime);
            _grundle.Draw(gameTime);
        }

        #endregion

        #region Private Methods

        private void CreateScenarios()
        {
            var scenarios = new List<Scenario>();

            using (var scenariosQuery = new PlQuery("cenario(C, Cor), cor(Cor, R, G, B), posicao(X, Y, C, 0)"))
            {
                foreach (var scenarioData in scenariosQuery.SolutionVariables)
                {
                    var name = scenarioData["C"].ToString();
                    var x = Convert.ToInt32(scenarioData["X"].ToString());
                    var y = Convert.ToInt32(scenarioData["Y"].ToString());
                    var color = new Color(Convert.ToInt32(scenarioData["R"].ToString()), Convert.ToInt32(scenarioData["G"].ToString()), Convert.ToInt32(scenarioData["B"].ToString()));

                    var scenario = new Scenario(_bounds, name, color, new Vector2(x, y));
                    scenarios.Add(scenario);
                }
            }

            foreach (var scenario in scenarios)
            {
                this.CreateWalls(scenario);
                this.CreateGates(scenario);
            }

            _scenarios = scenarios;

            using (var currentScenarioQuery = new PlQuery("cenarioCorrente(C, 0)"))
            {
                var currentScenarioData = currentScenarioQuery.SolutionVariables.First();
                var currentScenarioName = currentScenarioData["C"].ToString();
                _currentScenario = _scenarios.First(s => s.Name == currentScenarioName);
                _lastCurrentScenario = _currentScenario;
            }
        }

        private void CreateWalls(Scenario scenario)
        {
            using (var wallsQuery = new PlQuery(string.Format("parede(Par, {0}), retangulo(Par, L, A), posicao(X, Y, Par, 0)", scenario.Name)))
            {
                foreach (var wallData in wallsQuery.SolutionVariables)
                {
                    var name = wallData["Par"].ToString();
                    var width = Convert.ToInt32(wallData["L"].ToString());
                    var height = Convert.ToInt32(wallData["A"].ToString());
                    var x = Convert.ToInt32(wallData["X"].ToString());
                    var y = Convert.ToInt32(wallData["Y"].ToString());
                    var originalBounds = new Rectangle(x, y, width, height);
                    x -= (int)scenario.Offset.X;
                    y -= (int)scenario.Offset.Y;
                    var bounds = new Rectangle(x, y, width, height);

                    var wall = new Wall(bounds, originalBounds, name, scenario.Color);
                    scenario.AddWall(wall);
                }
            }
        }

        private void CreateGates(Scenario scenario)
        {
            using (var gatesQuery = new PlQuery(string.Format("portao(Por, Cor), retangulo(Por, L, A), posicao(X, Y, Por, 0), encosta(Por, {0}, 0)", scenario.Name)))
            {
                foreach (var gate in gatesQuery.SolutionVariables)
                {
                    var name = gate["Por"].ToString();
                    var x = Convert.ToInt32(gate["X"].ToString()) - (int)scenario.Offset.X;
                    var y = Convert.ToInt32(gate["Y"].ToString()) - (int)scenario.Offset.Y;
                    var width = Convert.ToInt32(gate["L"].ToString());
                    var height = Convert.ToInt32(gate["A"].ToString());
                    var bounds = new Rectangle(x, y, width, height);

                    scenario.AddGate(new Gate(bounds, name));
                }
            }
        }

        private void CreateCharacter()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, personagem, 0), retangulo(personagem, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                _character = new Character(bounds);
            }
        }

        private void CreateItems()
        {
            _items = new List<Item>();

            this.CreateYellowKey();
            this.CreateSword();
            this.CreateBlackKey();
            this.CreateChalice();
        }

        private void CreateYellowKey()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, chaveAmarela, 0), retangulo(chaveAmarela, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                var yellowKey = new Item("chaveAmarela", "YellowKey", bounds);
                _items.Add(yellowKey);
            }
        }

        private void CreateBlackKey()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, chavePreta, 0), retangulo(chavePreta, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                var yellowKey = new Item("chavePreta", "BlackKey", bounds);
                _items.Add(yellowKey);
            }
        }

        private void CreateSword()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, espada, 0), retangulo(espada, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                var sword = new Item("espada", "Sword", bounds);
                _items.Add(sword);
            }
        }

        private void CreateChalice()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, calice, 0), retangulo(calice, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                var chalice = new Item("calice", "Chalice", bounds);
                _items.Add(chalice);
            }
        }

        private void CreateYorgle()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, yorgle, 0), retangulo(yorgle, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                _yorgle = new Dragon(bounds, "yorgle", @"Textures\Yorgle", @"Textures\YorgleBiting", @"Textures\YorgleDead");
            }
        }

        private void CreateGrundle()
        {
            using (var positionsQuery = new PlQuery("posicao(X, Y, grundle, 0), retangulo(grundle, Lar, Alt)"))
            {
                var position = positionsQuery.SolutionVariables.First();
                var x = Convert.ToInt32(position["X"].ToString());
                var y = Convert.ToInt32(position["Y"].ToString());
                var width = Convert.ToInt32(position["Lar"].ToString());
                var height = Convert.ToInt32(position["Alt"].ToString()); ;
                var bounds = new Rectangle(x, y, width, height);
                _grundle = new Dragon(bounds, "grundle", @"Textures\Grundle", @"Textures\GrundleBiting", @"Textures\GrundleDead");
            }
        }

        private void UpdateData()
        {
            _character.UpdateData(_currentScenario, _lastCurrentScenario, _time);

            foreach (var item in _items)
                item.UpdateData(_currentScenario, _lastCurrentScenario, _time);

            _yorgle.UpdateData(_currentScenario, _lastCurrentScenario, _time);
            _grundle.UpdateData(_currentScenario, _lastCurrentScenario, _time);

            foreach (var scenario in _scenarios)
                scenario.UpdateData(_time);

            PlQuery.PlCall(string.Format("retract(cenarioCorrente({0}, {1}))", _lastCurrentScenario.Name, _lastUpdateTime));
            PlQuery.PlCall(string.Format("asserta(cenarioCorrente({0}, {1}))", _currentScenario.Name, _time));

            PlQuery.PlCall(string.Format("retract(fimJogo({0}, {1}))", _lastUpdateTime, _gameEnded ? "1" : "0"));
            PlQuery.PlCall(string.Format("asserta(fimJogo({0}, {1}))", _time, _gameEnded ? "1" : "0"));

            _lastCurrentScenario = _currentScenario;
            _lastUpdateTime = _time;
        }

        #endregion
    }
}